#include "Latar.h"

/*maaf*/
/*tolong dipindah ke tempat semestinya*/
//ini kaya perkalian vektor 
float Sign(Point p1, Point p2, Point p3)
{
  return (p1.getAbsis() - p3.getAbsis()) * (p2.getOrdinat()- p3.getOrdinat()) 
          - (p2.getAbsis() - p3.getAbsis()) * (p1.getOrdinat() - p3.getOrdinat());
}

bool IsPointInTri(Point pt, Point v1, Point v2, Point v3)
{
  bool b1, b2, b3;

  b1 = Sign(pt, v1, v2) < 0;
  b2 = Sign(pt, v2, v3) < 0;
  b3 = Sign(pt, v3, v1) < 0;

  return ((b1 == b2) && (b2 == b3));
}
///

using namespace std;

//Empat Sekawan
Latar::Latar(int M, int N) {
    Row = M;
    Col = N;
    Kertas = new Sel[Row*Col];
    Gambar = new Bidang* ;
    MaxSize = 1;
    NBidang = 0;
    
    for (int i=0;i<M;++i) {
        for (int j=0;j<N;++j) {
            Kertas[i*N+j].setLocation(Point(i,j));
            Kertas[i*N+j].setContent(0);
        }
    }
}

Latar::Latar(const Latar& L) {
    MaxSize = L.MaxSize;
    NBidang = L.NBidang;
    Gambar = new Bidang* [MaxSize];
    for (int i=0;i<NBidang;++i) Gambar[i] = L.Gambar[i]; 
    
    Row = L.Row;
    Col = L.Col;
    Kertas = new Sel[Row*Col];
    
    for (int i=0;i<Row;++i) {
        for (int j=0;j<Col;++j) {
            Kertas[i*Col+j] = L.Kertas[i*Col+j];
        }
    }
}

Latar& Latar::operator=(const Latar& L) {
    if (this != &L) {
        delete[] Gambar;
        delete[] Kertas;
        
        MaxSize = L.MaxSize;
        NBidang = L.NBidang;
        Gambar = new Bidang* [MaxSize];
        for (int i=0;i<NBidang;++i) Gambar[i] = L.Gambar[i]; 
    
        Row = L.Row;
        Col = L.Col;
        Kertas = new Sel[Row*Col];
        
        for (int i=0;i<Row;++i) {
            for (int j=0;j<Col;++j) {
                Kertas[i*Col+j] = L.Kertas[i*Col+j];
            }
        }
    }
    return *this;
}

Latar::~Latar() {
    delete[] Kertas;
    delete[] Gambar;
}

//Getter
int Latar::GetRow() const {
    return Row;
}

int Latar::GetCol() const {
    return Col;
}

int Latar::GetNOBidang() const {
    return NBidang;
}

//Predicate
bool Latar::IsBidangIn(const Bidang* B) {
    bool flag = false;
    for (int i=0;(i<NBidang)&&(!flag);++i) {
        flag = (B == Gambar[i]);
    }
    return flag;
}

bool Latar::IsPointIn(Point P) {
    return ((P.getAbsis() > -1)&&(P.getAbsis() < Row)&&(P.getOrdinat() > -1)&&(P.getOrdinat() < Col));
}

//Other function needed
bool RelativeTurn(Point a, Point b, Point c) {
    return ((a.getAbsis()-b.getAbsis())*(a.getOrdinat()-c.getOrdinat())-(a.getAbsis()-c.getAbsis())*(a.getOrdinat()-b.getOrdinat()));
}
//Jika nilainya positif, maka B-C belok kiri dari A-B.
//Jika nilainya 0, berarti A-B-C segaris
//Jika nilainya negatif, berarti B-C belok kanan dari A-B.

//Inside mengecek apakah sebuah point x ada
//dalam segitiga yang dinyatakan dengan a,b,c
bool Inside(Point a, Point b, Point c, Point x) {
    return ((RelativeTurn(a,b,x) >= 0)&&(RelativeTurn(b,c,x) >= 0)&&(RelativeTurn(c,a,x) >= 0));
}

//Menghitung fpb dari dua bilangan
int gcd(int a,int b) {
    return (b)?gcd(b,a % b):a;
}

//Main Operation
void Latar::AddBidang(Bidang* B) {
    if (1) {
    //if (!IsBidangIn(B)) {//IsBidangIn masih ngebug (liat samnote.txt)
        if (NBidang == MaxSize) {
            MaxSize *= 2;
            Bidang** temp = Gambar;
            Gambar = new Bidang* [MaxSize];
            for (int i=0;i<NBidang;++i) Gambar[i] = temp[i];
            delete[] temp;
        }
        
        Gambar[NBidang] = B;
        ++NBidang;
    }
}

void Latar::DelBidang(const Bidang* B) {
    int indeks = 0;
    while ((indeks < NBidang) && (B != Gambar[indeks])) ++indeks;
    
    if (indeks < NBidang) {
        --NBidang;
        for (int i=indeks;i<NBidang;++i) Gambar[i] = Gambar[i+1];
        
        if (NBidang*4 == MaxSize) {
            MaxSize /= 2;
            Bidang** temp = Gambar;
            Gambar = new Bidang* [MaxSize];
            for (int i=0;i<NBidang;++i) Gambar[i] = temp[i];
            delete[] temp;            
        }
    }
}

void Latar::CleanPaper(char a) {
	for (int i=0;i<Row;++i) {
        for (int j=0;j<Col;++j) {
            Kertas[i*Col+j].setContent(a);
        }
    }
}

void Latar::ClearAll() {
    delete[] Gambar;
    NBidang = 0;
    MaxSize = 1;
    Gambar = new Bidang* [1];
    
    CleanPaper(0);
}

void Latar::Draw(char kosong) {
    CleanPaper(kosong);
    
    //Polygon yang ada ditriangulasi terlebih dahulu
    //Baru kemudian setiap bagian dalam segitiga diwarnai
    //Triangulasi dilakukan dengan ear-trimming algorithm
    //Kompleksitas = O(N^2) + O(N*R*C)
    
    for (int i=0;i<NBidang;++i) {
        char c = (*Gambar[i]).GetContent();
        int banyak = (*Gambar[i]).GetN();
        Point sudut[banyak];
        for (int j=0;j<banyak;++j) sudut[j] = (*Gambar[i]).GetSudutN(j);
        
        int next[banyak];
        bool sudah[banyak];
        memset(sudah,0,sizeof(sudah));
        for (int j=0;j<banyak;++j) next[j] = j+1;
        next[banyak-1] = 0;
        int indeks = 0;

        for (int j=banyak;j>2;--j) {
            bool flag = false;
            while (!flag) {
                //cout << "j : " << j << " indeks : " << indeks << endl;
                
                Point u = sudut[indeks];
                
                Point v = sudut[next[indeks]];
                Point w = sudut[next[next[indeks]]];
                if (RelativeTurn(u,v,w) < 0) flag = true;
                
                for (int k=0;(k<banyak)&&(!flag);++k){
                    if (sudah[k]) continue;
                    if ((k==indeks)||(k==next[indeks])||(k==next[next[indeks]])) continue;
                    flag = flag&&IsPointInTri(sudut[k],u,v,w);
                    //Inside(u,v,w,sudut[k]);
                    //mesti diretain ga ini ? tadinya flag = Inside(u,v,w,sudut[k]);
                }                
                              
                if (flag) {
                    indeks = next[indeks];
                    flag = false;
                } else {
                    //cout << "mulai ngewarnain mamen\n";
                    flag = true;
                    sudah[next[indeks]] = true;
                    next[indeks] = next[next[indeks]];
                    
                    //cout << "u " << u << " |v " << v << " |w " << w << endl;
                    for (int k=0;k<Row;++k) {
                        for (int l=0;l<Col;++l) {
                            if (IsPointInTri(Point(k,l),u,v,w)){
                            //if (Inside(u,v,w,Point(k,l))) { 
                                //cout << "Point " << Point(k,l) << endl;                                
                                Kertas[k*Col+l].setContent(c);
                            }
                        }
                    }
                }
            }
        }
    }
    
    ShowLatar();
}

void Latar::ShowLatar() {
    for (int i=0;i<Row;++i) {
        for (int j=0;j<Col;++j) {
            cout<<Kertas[i*Col+j].getContent();
        }
        cout<<endl;
    }
}

void Latar::ComputeC() {
    int blank = 0, nonblank = 0;
    for (int i=0;i<Row;++i) {
        for (int j=0;j<Col;++j) {
            if (Kertas[i*Col+j].getContent()!='.') ++nonblank;
            else ++blank;
        }
    }

    int temp = gcd(blank, nonblank);
    blank /= temp;
    nonblank /= temp;
    cout<<nonblank<<" / "<<blank<<endl;
}

void Latar::ComputeS() {
    int nonblank = 0;
    for (int i=0;i<Row;++i) {
        for (int j=0;j<Col;++j) {
            if (Kertas[i*Col+j].getContent()=='.') ++nonblank;
        }
    }

    int luas = Row * Col;
    int temp = gcd(luas, nonblank);
    luas /= temp;
    nonblank /= temp;
    cout<<nonblank<<" / "<<luas<<endl;    
}

ostream& operator<< (ostream& stream, const Latar& L){
    stream << "##-----" << endl;
    stream << "row     : " << L.Row << "  col : " << L.Col << endl;
    stream << "nBidang : " << L.NBidang << endl;
    stream << "MaxSize : " << L.MaxSize << endl;
    
    for (int i = 0; i < L.NBidang; i++){
        stream << "   --- bidang ke-" << i << endl;
        stream << *L.Gambar[i] <<  endl << "   ---" <<endl;
    }
    stream << "##-----" << endl;
    
}


